﻿using System.ComponentModel;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using Bzstd.Entity;
using Bzstd.Utility;
using Bzstd.Utility.Assertion;
using Steamworks;
using static Bzstd.Utility.ConfigHelper;

namespace Bzstd.Service;

public class SteamService
{
	private readonly ILogger<SteamService> _logger;

	private readonly AppId_t consumerAppId = new(ConfigHelper.GameInfo.ConsumerAppId);
	private readonly AppId_t creatorAppId = new(ConfigHelper.GameInfo.CreatorAppId);
	private readonly AppId_t[] searchAppIds = ConfigHelper.GameInfo.SearchAppIds.Select(x => new AppId_t(x)).ToArray();
	private readonly WorkshopType workshopType = ConfigHelper.GameInfo.WorkshopType;

	/// <summary>
	/// 用户SteamId
	/// </summary>
	private readonly string steamId;

	/// <summary>
	/// 是否是开发者
	/// </summary>
	private readonly bool isDev;

	/// <summary>
	/// 游戏安装路径
	/// </summary>
	private readonly string gamePath;

	/// <summary>
	/// 用户数据路径
	/// </summary>
	private readonly string userDataPath;

	private readonly string workshopContentPath;

	/// <summary>
	/// 截图路径
	/// </summary>
	private readonly string screenshotPath;

	public List<PuzzleItem> PuzzleItems { get; } = [];

	public SteamService(ILogger<SteamService> logger)
	{
		_logger = logger;
		string steamApiPath = Path.Combine(ProductPath, "steam_appid.txt");
		File.WriteAllText(steamApiPath, creatorAppId.ToString());

		// 初始化
		if (!SteamAPI.IsSteamRunning() || !SteamAPI.Init())
		{
			MessageBox.Show("Steam Workshop Kit初始化失败：Steam未运行！", "提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
			File.Delete(steamApiPath);
			Environment.Exit(1);
		}

		File.Delete(steamApiPath);

		// 程序退出时关闭
		AppDomain.CurrentDomain.ProcessExit += (sender, args) => SteamAPI.Shutdown();

		SteamApps.GetAppInstallDir(consumerAppId, out gamePath, 255);
		workshopContentPath = Path.GetFullPath(Path.Combine(gamePath, "..", "..", "workshop", "content", consumerAppId.ToString()));
		steamId = SteamUser.GetSteamID().ToString();
		isDev = SteamUser.GetSteamID().GetAccountID().m_AccountID == 314324746;
		SteamUser.GetUserDataFolder(out userDataPath, 255);
		screenshotPath = Path.GetFullPath(Path.Combine(userDataPath, "..", "..", "760", "remote", consumerAppId.ToString(), "screenshots"));

		Task.Run(async delegate
		{
			while (true)
			{
				await Task.Delay(TimeSpan.FromSeconds(0.1));
				SteamAPI.RunCallbacks();
			}
			// ReSharper disable once FunctionNeverReturns
		});

		Init();
	}

	public void Init()
	{
		lock (this)
		{
			_logger.LogInformation("Init start");

			PuzzleItems.Clear();
			PuzzleItems.AddRange(QueryInternal((creator, page) => SteamUGC.CreateQueryUserUGCRequest(
				SteamUser.GetSteamID().GetAccountID(),
				EUserUGCList.k_EUserUGCList_Published,
				EUGCMatchingUGCType.k_EUGCMatchingUGCType_All,
				EUserUGCListSortOrder.k_EUserUGCListSortOrder_CreationOrderDesc,
				creator,
				consumerAppId,
				page
			), "创意工坊物品"));

			_logger.LogInformation("Init finish: {GameInfo}", ConfigHelper.GameInfo);
		}
	}

	private List<PuzzleItem> QueryInternal(Func<AppId_t, uint, UGCQueryHandle_t> handler, string description)
	{
		var result = new List<PuzzleItem>();
		foreach (var creator in searchAppIds)
		{
			uint page = 1;
			while (page > 0)
			{
				var queryHandle = handler(creator, page);
				if (queryHandle == UGCQueryHandle_t.Invalid)
				{
					break;
				}

				_logger.LogInformation("开始查询【{Description}】 {Creator}: 第{Page}页", description, creator, page);
				SteamUGC.SetLanguage(queryHandle, "english"); // 语言 english
				SteamUGC.SetReturnLongDescription(queryHandle, true); // 查询长描述
				SteamUGC.SetReturnChildren(queryHandle, true); // 查询子物品
				var queryResult = CallResultHandle<SteamUGCQueryCompleted_t>.Call(SteamUGC.SendQueryUGCRequest(queryHandle));
				Assert.IsTrue(queryResult.m_eResult == EResult.k_EResultOK, queryResult.m_eResult.ToString());
				_logger.LogInformation("查询完成【{Description}】 {Creator}: 第{Page}页: {Result}条记录", description, creator, page, queryResult.m_unNumResultsReturned);

				if (queryResult.m_unTotalMatchingResults > page * 50 && queryResult.m_unNumResultsReturned <= 50)
				{
					page++;
				}
				else
				{
					page = 0;
				}

				for (uint i = 0; i < queryResult.m_unNumResultsReturned; i++)
				{
					SteamUGC.GetQueryUGCResult(queryHandle, i, out var details);
					if (details.m_eFileType == EWorkshopFileType.k_EWorkshopFileTypeCollection)
					{
						var ids = new PublishedFileId_t[details.m_unNumChildren];
						SteamUGC.GetQueryUGCChildren(queryHandle, i, ids, details.m_unNumChildren);
						result.AddRange(
							QueryInternal(
								(creator, page) => SteamUGC.CreateQueryUGCDetailsRequest(ids, (uint) ids.Length),
								details.m_nPublishedFileId.m_PublishedFileId + "子物品"
							)
						);
					}
					else
					{
						var workshopItem = new PuzzleItem
						{
							Visibility = (uint) details.m_eVisibility,
							FileId = details.m_nPublishedFileId.m_PublishedFileId,
							Tags = details.m_rgchTags.Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries),
							Title = details.m_rgchTitle,
							Description = details.m_rgchDescription,
							CreateTime = DateTimeOffset.FromUnixTimeSeconds(details.m_rtimeCreated).ToLocalTime(),
							UpdateTime = DateTimeOffset.FromUnixTimeSeconds(details.m_rtimeUpdated).ToLocalTime(),
							MapFileName = Path.GetFileName(details.m_pchFileName),
							MapFileSize = (uint) details.m_nFileSize
						};
						SteamUGC.GetQueryUGCPreviewURL(queryHandle, i, out string preview, 255);
						workshopItem.Preview = preview;
						result.Add(workshopItem);
					}
				}

				SteamUGC.ReleaseQueryUGCRequest(queryHandle);
			}
		}

		return result;
	}

	public object OpenPreviewFile()
	{
		string result = null;
		bool returned = false;
		AppInitializer.MainForm.BeginInvoke(() =>
		{
			var fileDialog = new OpenFileDialog
			{
				Filter = "图片文件 (*.jpg;*.jpeg) | *.jpg;*.jpeg",
				CheckFileExists = true,
				InitialDirectory = screenshotPath,
				RestoreDirectory = false,
				Multiselect = false
			};
			fileDialog.ShowDialog(AppInitializer.MainForm);
			result = fileDialog.FileName;
			returned = true;
		});
		CommonUtility.WaitUntil(() => returned).Wait();
		byte[] hash = MD5.HashData(Encoding.UTF8.GetBytes(result));
		string md5 = Convert.ToHexString(hash);
		AppInitializer.HashMapping[md5] = result;
		return string.IsNullOrEmpty(result)
			? null
			: new
			{
				Path = result,
				Url = EncodeUrl("/res/" + md5 + "/" + Path.GetFileName(result))
			};
	}

	public object OpenMapFile()
	{
		string result = null;
		bool returned = false;
		AppInitializer.MainForm.BeginInvoke(() =>
		{
			var fileDialog = new OpenFileDialog
			{
				CheckFileExists = true,
				RestoreDirectory = false,
				Multiselect = false
			};
			fileDialog.ShowDialog(AppInitializer.MainForm);
			result = fileDialog.FileName;
			returned = true;
		});
		CommonUtility.WaitUntil(() => returned).Wait();
		return string.IsNullOrEmpty(result)
			? null
			: new
			{
				Path = result,
				FileName = Path.GetFileName(result),
				FileSize = new FileInfo(result).Length
			};
	}

	public object OpenFolder()
	{
		string result = null;
		bool returned = false;
		AppInitializer.MainForm.BeginInvoke(() =>
		{
			var folderBrowserDialog = new FolderBrowserDialog();
			folderBrowserDialog.ShowDialog(AppInitializer.MainForm);
			result = folderBrowserDialog.SelectedPath;
			returned = true;
		});
		CommonUtility.WaitUntil(() => returned).Wait();
		return string.IsNullOrEmpty(result)
			? null
			: new
			{
				Path = result,
				FileName = Path.GetFileName(result),
				FileSize = Directory.GetFiles(result, "*", SearchOption.AllDirectories).Sum(f => new FileInfo(f).Length)
			};
	}

	private void UploadRemoteStorage(PuzzleItem puzzleItem)
	{
		if (string.IsNullOrEmpty(puzzleItem.MapFilePath))
		{
			return;
		}

		var fileId = new PublishedFileId_t(puzzleItem.FileId);

		string mapFileName = Path.GetFileName(puzzleItem.MapFilePath);

		_logger.LogInformation("写关卡文件开始");
		var data = File.ReadAllBytes(puzzleItem.MapFilePath);
		var writeResult = SteamRemoteStorage.FileWrite(mapFileName, data, data.Length);
		Assert.IsTrue(writeResult, "写关卡文件失败");
		_logger.LogInformation("写关卡文件完成");

		_logger.LogInformation("发布关卡文件开始");
		var updateHandle = SteamRemoteStorage.CreatePublishedFileUpdateRequest(fileId);
		SteamRemoteStorage.UpdatePublishedFileFile(updateHandle, mapFileName);
		var ca = SteamRemoteStorage.CommitPublishedFileUpdate(updateHandle);
		var result = CallResultHandle<RemoteStoragePublishFileResult_t>.Call(ca);
		Assert.IsTrue(result.m_eResult == EResult.k_EResultOK, result.m_eResult.ToString());
		if (result.m_bUserNeedsToAcceptWorkshopLegalAgreement)
		{
			SteamFriends.ActivateGameOverlayToWebPage("https://steamcommunity.com/sharedfiles/workshoplegalagreement");
		}
		_logger.LogInformation("发布关卡文件完成");

		SteamRemoteStorage.FileDelete(mapFileName);
	}

	public void Submit(PuzzleItem puzzleItem)
	{
		var fileId = new PublishedFileId_t(puzzleItem.FileId);
		var tags = puzzleItem.Tags?.ToHashSet() ?? [];

		_logger.LogInformation("发布开始");

		if (puzzleItem.FileId == 0 && !string.IsNullOrEmpty(puzzleItem.MapFilePath))
		{
			var result = CallResultHandle<CreateItemResult_t>.Call(SteamUGC.CreateItem(consumerAppId, EWorkshopFileType.k_EWorkshopFileTypeCommunity));
			fileId = result.m_nPublishedFileId;
			puzzleItem.FileId = fileId.m_PublishedFileId;
			if (result.m_bUserNeedsToAcceptWorkshopLegalAgreement)
			{
				SteamFriends.ActivateGameOverlayToWebPage("https://steamcommunity.com/sharedfiles/workshoplegalagreement");
			}
		}

		var updateHandle = SteamUGC.StartItemUpdate(consumerAppId, fileId);

		if (!string.IsNullOrEmpty(puzzleItem.MapFilePath))
		{
			if (workshopType == WorkshopType.SteamRemoteStorage)
			{
				UploadRemoteStorage(puzzleItem);
			}
			else
			{
				SteamUGC.SetItemContent(updateHandle, puzzleItem.MapFilePath);
			}
		}

		if (!string.IsNullOrEmpty(puzzleItem.PreviewFilePath))
		{
			SteamUGC.SetItemPreview(updateHandle, puzzleItem.PreviewFilePath);
		}

		SteamUGC.SetItemTitle(updateHandle, puzzleItem.Title);
		SteamUGC.SetItemDescription(updateHandle, puzzleItem.Description);
		SteamUGC.SetItemTags(updateHandle, new List<string>(tags));
		SteamUGC.SetItemVisibility(updateHandle, (ERemoteStoragePublishedFileVisibility) puzzleItem.Visibility);
		var ca = SteamUGC.SubmitItemUpdate(updateHandle, string.Empty);
		var resultUpdate = CallResultHandle<SubmitItemUpdateResult_t>.Call(ca);
		Assert.IsTrue(resultUpdate.m_eResult == EResult.k_EResultOK, resultUpdate.m_eResult.ToString());
		if (resultUpdate.m_bUserNeedsToAcceptWorkshopLegalAgreement)
		{
			SteamFriends.ActivateGameOverlayToWebPage("https://steamcommunity.com/sharedfiles/workshoplegalagreement");
		}

		_logger.LogInformation("发布完成");

		var resultUnsub = CallResultHandle<RemoteStorageUnsubscribePublishedFileResult_t>.Call(SteamUGC.UnsubscribeItem(fileId));
		Assert.IsTrue(resultUnsub.m_eResult == EResult.k_EResultOK, resultUnsub.m_eResult.ToString());
		var resultSub = CallResultHandle<RemoteStorageSubscribePublishedFileResult_t>.Call(SteamUGC.SubscribeItem(fileId));
		Assert.IsTrue(resultSub.m_eResult == EResult.k_EResultOK, resultUnsub.m_eResult.ToString());
		ShowInWorkshop(fileId.m_PublishedFileId);
		Init();
	}

	public static string EncodeUrl(string url)
	{
		string[] splits = url.Split(PathSeparators, TrimAndRemoveEmptyEntries);
		string urlEncoded = string.Join('/', splits.Select(Uri.EscapeDataString));
		return urlEncoded;
	}

	public void ExecCommand(string command)
	{
		if (command == "screenshot")
		{
			Process.Start("explorer", "/root," + screenshotPath);
		}
	}

	public void ShowInWorkshop(ulong fileId)
	{
		SteamFriends.ActivateGameOverlayToWebPage("steam://url/CommunityFilePage/" + fileId);
	}

	public void ShowInLocalDirectory(ulong fileId)
	{
		Process.Start("explorer", "/root," + Path.Combine(workshopContentPath, fileId.ToString()));
	}

	public PuzzleItem QueryWorkshopItem(ulong fileId)
	{
		_logger.LogInformation("开始查询{FileId}", fileId);
		var queryHandle = SteamUGC.CreateQueryUGCDetailsRequest([new PublishedFileId_t(fileId)], 1);
		SteamUGC.SetLanguage(queryHandle, "english"); // 语言 english
		SteamUGC.SetReturnLongDescription(queryHandle, true); // 查询长描述
		var queryResult = CallResultHandle<SteamUGCQueryCompleted_t>.Call(SteamUGC.SendQueryUGCRequest(queryHandle));
		Assert.IsTrue(queryResult.m_eResult == EResult.k_EResultOK, queryResult.m_eResult.ToString());
		Assert.IsTrue(queryResult.m_unNumResultsReturned == 1, "未查询到" + fileId);
		_logger.LogInformation("查询完成{FileId}", fileId);

		SteamUGC.GetQueryUGCResult(queryHandle, 0, out var details);
		var workshopItem = new PuzzleItem
		{
			Visibility = (uint) details.m_eVisibility,
			FileId = details.m_nPublishedFileId.m_PublishedFileId,
			Tags = details.m_rgchTags.Split(",", TrimAndRemoveEmptyEntries),
			Title = details.m_rgchTitle,
			Description = details.m_rgchDescription,
			CreateTime = DateTimeOffset.FromUnixTimeSeconds(details.m_rtimeCreated).ToLocalTime(),
			UpdateTime = DateTimeOffset.FromUnixTimeSeconds(details.m_rtimeUpdated).ToLocalTime(),
			MapFileName = Path.GetFileName(details.m_pchFileName),
			MapFileSize = (uint) details.m_nFileSize
		};
		SteamUGC.GetQueryUGCPreviewURL(queryHandle, 0, out string preview, 255);
		workshopItem.Preview = preview;
		return workshopItem;
	}

	public void CloneMap(PuzzleItem puzzleItem)
	{
		Assert.IsTrue(workshopType == WorkshopType.SteamRemoteStorage, "不支持克隆");
		var oldFileId = puzzleItem.FileId;
		_logger.LogInformation("开始查询{FileId}", oldFileId);
		var queryHandle = SteamUGC.CreateQueryUGCDetailsRequest([new PublishedFileId_t(oldFileId)], 1);
		var queryResult = CallResultHandle<SteamUGCQueryCompleted_t>.Call(SteamUGC.SendQueryUGCRequest(queryHandle));
		Assert.IsTrue(queryResult.m_eResult == EResult.k_EResultOK, queryResult.m_eResult.ToString());
		Assert.IsTrue(queryResult.m_unNumResultsReturned == 1, "未查询到" + oldFileId);
		_logger.LogInformation("查询完成{FileId}", oldFileId);

		SteamUGC.GetQueryUGCResult(queryHandle, 0, out var details);
		Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "stwk"));
		string tempMapPath = Path.Combine(Path.GetTempPath(), "stwk", Path.GetFileName(details.m_pchFileName));
		string tempPreviewPath = Path.Combine(Path.GetTempPath(), "stwk", "preview.jpg");

		{
			_logger.LogInformation("下载关卡文件开始");
			var ca = SteamRemoteStorage.UGCDownloadToLocation(details.m_hFile, tempMapPath, 1);
			var result = CallResultHandle<RemoteStorageDownloadUGCResult_t>.Call(ca);
			Assert.IsTrue(result.m_eResult == EResult.k_EResultOK, result.m_eResult.ToString());
			_logger.LogInformation("下载关卡文件完成");
		}

		{
			_logger.LogInformation("下载预览图文件开始");
			var ca = SteamRemoteStorage.UGCDownloadToLocation(details.m_hPreviewFile, tempPreviewPath, 1);
			var result = CallResultHandle<RemoteStorageDownloadUGCResult_t>.Call(ca);
			Assert.IsTrue(result.m_eResult == EResult.k_EResultOK, result.m_eResult.ToString());
			_logger.LogInformation("下载预览图文件完成");
		}

		puzzleItem.MapFilePath = tempMapPath;
		puzzleItem.PreviewFilePath = tempPreviewPath;
		puzzleItem.FileId = 0;
		Submit(puzzleItem);
		Directory.Delete(Path.Combine(Path.GetTempPath(), "stwk"), true);
	}

	public List<PuzzleItem> Query(QueryInfo queryInfo)
	{
		switch (queryInfo.QueryWay)
		{
			case "author":
			{
				AccountID_t accountId;
				if (!string.IsNullOrWhiteSpace(queryInfo.AuthorUrl))
				{
					if (!uint.TryParse(queryInfo.AuthorUrl, out uint accountIdValue))
					{
						const string pattern1 = "<div class=\"playerAvatar profile_header_size";
						const string pattern2 = "data-miniprofile=\"";
						const string pattern3 = "\">";
						var result = new HttpClient().GetAsync(queryInfo.AuthorUrl).Result;
						var content = result.Content.ReadAsStringAsync().Result;
						int pos = content.IndexOf(pattern1);
						int start = content.IndexOf(pattern2, pos) + pattern2.Length;
						int end = content.IndexOf(pattern3, start);
						accountIdValue = uint.Parse(content[start..end]);
					}
					accountId = new AccountID_t(accountIdValue);
				}
				else
				{
					accountId = SteamUser.GetSteamID().GetAccountID();
				}

				var ugcListType = queryInfo.AuthorType switch
				{
					"subscribed" => EUserUGCList.k_EUserUGCList_Subscribed,
					"published" => EUserUGCList.k_EUserUGCList_Published,
					"favorited" => EUserUGCList.k_EUserUGCList_Favorited,
					"willVoteLater" => EUserUGCList.k_EUserUGCList_WillVoteLater,
					"votedOn" => EUserUGCList.k_EUserUGCList_VotedOn,
					"votedUp" => EUserUGCList.k_EUserUGCList_VotedUp,
					"votedDown" => EUserUGCList.k_EUserUGCList_VotedDown,
					"usedOrPlayed" => EUserUGCList.k_EUserUGCList_UsedOrPlayed,
					"followed" => EUserUGCList.k_EUserUGCList_Followed,
					_ => throw new InvalidEnumArgumentException()
				};

				return QueryInternal((creator, page) => SteamUGC.CreateQueryUserUGCRequest(
					accountId,
					ugcListType,
					EUGCMatchingUGCType.k_EUGCMatchingUGCType_All,
					EUserUGCListSortOrder.k_EUserUGCListSortOrder_CreationOrderDesc,
					creator,
					consumerAppId,
					page
				), accountId + " " + queryInfo.AuthorType);
			}
			case "item":
			{
				string[] urls = Regex.Split(queryInfo.ItemUrl, "\\s+");
				return QueryInternal((creator, page) => creator.m_AppId == consumerAppId.m_AppId
					? SteamUGC.CreateQueryUGCDetailsRequest(
						urls.Select(url => new PublishedFileId_t(ulong.Parse(url.Split("?id=")[^1]))).ToArray(),
						(uint) urls.Length
					)
					: UGCQueryHandle_t.Invalid, urls.Length + "物品");
			}
			default:
			{
				return null;
			}
		}
	}

	public void Operate(ulong fileId, string type)
	{
		switch (type)
		{
			case "subscribe":
			{
				var ca = SteamUGC.SubscribeItem(new PublishedFileId_t(fileId));
				var result = CallResultHandle<RemoteStorageSubscribePublishedFileResult_t>.Call(ca);
				Assert.IsTrue(result.m_eResult == EResult.k_EResultOK, result.m_eResult.ToString());
				_logger.LogInformation("订阅完成: {FileId}", fileId);
				break;
			}
			case "unsubscribe":
			{
				var ca = SteamUGC.UnsubscribeItem(new PublishedFileId_t(fileId));
				var result = CallResultHandle<RemoteStorageUnsubscribePublishedFileResult_t>.Call(ca);
				Assert.IsTrue(result.m_eResult == EResult.k_EResultOK, result.m_eResult.ToString());
				_logger.LogInformation("取消订阅完成: {FileId}", fileId);
				break;
			}
			case "favorite":
			{
				var ca = SteamUGC.AddItemToFavorites(consumerAppId, new PublishedFileId_t(fileId));
				var result = CallResultHandle<UserFavoriteItemsListChanged_t>.Call(ca);
				Assert.IsTrue(result.m_eResult == EResult.k_EResultOK, result.m_eResult.ToString());
				_logger.LogInformation("收藏完成: {FileId}", fileId);
				break;
			}
			case "unfavorite":
			{
				var ca = SteamUGC.RemoveItemFromFavorites(consumerAppId, new PublishedFileId_t(fileId));
				var result = CallResultHandle<UserFavoriteItemsListChanged_t>.Call(ca);
				Assert.IsTrue(result.m_eResult == EResult.k_EResultOK, result.m_eResult.ToString());
				_logger.LogInformation("取消收藏完成: {FileId}", fileId);
				break;
			}
			case "voteUp":
			{
				var ca = SteamUGC.SetUserItemVote(new PublishedFileId_t(fileId), true);
				var result = CallResultHandle<SetUserItemVoteResult_t>.Call(ca);
				Assert.IsTrue(result.m_eResult == EResult.k_EResultOK, result.m_eResult.ToString());
				_logger.LogInformation("好评完成: {FileId}", fileId);
				break;
			}
			case "voteDown":
			{
				var ca = SteamUGC.SetUserItemVote(new PublishedFileId_t(fileId), false);
				var result = CallResultHandle<SetUserItemVoteResult_t>.Call(ca);
				Assert.IsTrue(result.m_eResult == EResult.k_EResultOK, result.m_eResult.ToString());
				_logger.LogInformation("差评完成: {FileId}", fileId);
				break;
			}
			case "markAsPlayed":
			{
				var ca = SteamRemoteStorage.SetUserPublishedFileAction(new PublishedFileId_t(fileId), EWorkshopFileAction.k_EWorkshopFileActionPlayed);
				var result = CallResultHandle<RemoteStorageSetUserPublishedFileActionResult_t>.Call(ca);
				Assert.IsTrue(result.m_eResult == EResult.k_EResultOK, result.m_eResult.ToString());
				_logger.LogInformation("标记为玩过完成: {FileId}", fileId);
				break;
			}
		}
	}
}
